#include "pam_pg.h"
#include "pgconn.h"

/* ****************************************
 * public: pam_sm_authenticate
 * Function for PAM PostgreSQL authentication
 * **************************************** */
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, 
                                   int argc, const char *argv[]) {

   const char *user=NULL, *rhost=NULL;
   char *passwd,*salt,*crypt,*sha1,*cryptmd5,*md5,*md5b64;
   CONNECTION_DB conn;
   modopt_t modopt;
   int retval,verifypass=0;

   modopt = mod_options(argc, argv);

   if(modopt->debug >= 1) {
      SYSLOG("starting authenticate module");
   }

   retval = pam_get_user(pamh, (const char **)&user, NULL);
   if(retval != PAM_SUCCESS) {

      if(modopt->debug >= 3) {
         SYSLOG("couldn't get username");
      }

      return PAM_AUTH_ERR;
   }

   if(user==NULL) {

      if(modopt->debug >= 3) {
         SYSLOG("username is NULL, ignoring this pam module");
      }

      return PAM_IGNORE;
   }

   retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost );
   if(retval != PAM_SUCCESS) 
      rhost=NULL;

   if(get_passwd(pamh, &passwd, PAM_AUTHTOK, PASSWD_PROMPT) != PAM_SUCCESS) {
      return PAM_AUTH_ERR;
   }

   conn = init_db(modopt);
   if(conn == NULL) {

      if(modopt->debug >= 2) {
         SYSLOG("connection string is null, ignoring this module");
      }

      return PAM_IGNORE;
   }

SYSLOG("pegando user id");
   int id = get_user_id(conn, user);
   if(id == -1) {

      if(modopt->debug >= 2) {
         SYSLOG("user unknown on database, ignoring this module");
      }

      free_db(conn);
      return PAM_IGNORE;

   }

   sha1 = encrypt_password(PW_SHA1, passwd, NULL);
   verifypass = verify_password(conn, user, sha1);

   if(verifypass == 0) {

      salt = (char *) get_crypt_salt(conn, (char *) user, PW_CRYPT);

      if(salt != NULL && strlen(salt) > 1) {

         crypt = encrypt_password(PW_CRYPT, passwd, salt);
         verifypass = verify_password(conn, user, crypt);

      }

      if(verifypass == 1) {

         modify_password(conn, user, sha1);

      } else {

         char salt_md5[12];
         salt = (char *) get_crypt_salt(conn, (char *) user, PW_CRYPT_MD5);
         if(salt != NULL && strlen(salt) > 7) {

            sprintf(salt_md5, "$1$%s", salt);
            cryptmd5 = encrypt_password(PW_CRYPT_MD5, passwd, salt_md5);
            verifypass = verify_password(conn, user, cryptmd5);

         }

         if(verifypass == 1) {

            modify_password(conn, user, sha1);

         } else {

            md5b64 = encrypt_password(PW_MD5_B64, passwd, NULL);
            verifypass = verify_password(conn, user, md5b64);

            if(verifypass == 1) {

               modify_password(conn, user, sha1);

            } else {

               md5 = encrypt_password(PW_MD5, passwd, NULL);
               verifypass = verify_password(conn, user, md5);

               if(verifypass == 1) {

                  modify_password(conn, user, sha1);

               } 

            }

         }

      }

   }

   free_db(conn);

   if( verifypass != 1) {

      if(modopt->debug >= 1) {
         SYSLOG("authentication failed for '%s'", user);
      }
      return PAM_IGNORE;

   }

   if(modopt->debug >= 1) {
      SYSLOG("authentication success for '%s'", user);
   }

   return PAM_SUCCESS;

}

/* ****************************************
 * pam_sm_acct_mgmt
 * Funcao que verifica conta expirada ou 
 * mudanca de senha
 * **************************************** */
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
				int argc, const char **argv) {

   const char *user=NULL, *rhost=NULL;
   CONNECTION_DB conn;
   modopt_t modopt;
   int retval,verifyexpired=1/*,inactive=1*/;

   modopt = mod_options(argc, argv);

   if(modopt->debug >= 1) {
      SYSLOG("starting management module");
   }

   retval = pam_get_user(pamh, (const char **)&user, NULL);
   if(retval != PAM_SUCCESS) {

      if(modopt->debug >= 3) {
         SYSLOG("couldn't get username");
      }
      return PAM_AUTH_ERR;

   }

   if(user==NULL) {

      if(modopt->debug >= 3) {
         SYSLOG("username is NULL, ignoring this pam module");
      }
      return PAM_IGNORE;

   }

   retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost );
      if(retval != PAM_SUCCESS)
         return PAM_SYSTEM_ERR;

   conn = init_db(modopt);
   if(conn == NULL) {

      if(modopt->debug >= 2) {
         SYSLOG("connection string is null, ignoring this module");
      }
      return PAM_IGNORE;

   }

   verifyexpired = verify_expired(conn, user);
   //inactive = verify_inactive(conn, user);

   int id = get_user_id(conn, user);
   free_db(conn);

   if(id == -1) {

      if(modopt->debug >= 2) {
         SYSLOG("user unknown on database, ignoring this module");
      }
      return PAM_IGNORE;

   }

   if( verifyexpired == 0 ) {
     printf(ACCOUNT_EXPIRED);
     return PAM_ACCT_EXPIRED; 
   }

   if(modopt->debug >= 2) {
      SYSLOG("management success for '%s'", user);
   }

   return PAM_SUCCESS;

}

/* ****************************************
 * pam_sm_chauthok
 * Funcao que troca a senha
 * ****************************************/
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
				 int argc, const char **argv) {

   const char *user=NULL, *rhost=NULL;
   char *passwd, *newpasswd, *confirmpasswd;
   const void *conv=NULL, *oldauth=NULL;;
   CONNECTION_DB conn;
   modopt_t modopt;
   int retval,verifypass=0;

   modopt = mod_options(argc, argv);

   retval = pam_get_user(pamh, (const char **)&user, NULL);
   if(retval != PAM_SUCCESS) {
      return PAM_AUTH_ERR;
   }

   retval = pam_get_item(pamh, PAM_RHOST, (const void**) &rhost );
   if(retval != PAM_SUCCESS)
      rhost=NULL;

   retval = pam_get_item(pamh, PAM_CONV, (const void**) &conv );
   if(retval != PAM_SUCCESS) {
      return PAM_CONV_ERR;
   }
   
   conn = init_db(modopt);
   if(conn == NULL) {

      if(modopt->debug >= 2) {
         SYSLOG("connection string is null, ignoring this module");
      }
      return PAM_IGNORE;

   }
   int id = get_user_id(conn, user);
   free_db(conn);

   if(id == -1) {

      if(modopt->debug >= 2) {
         SYSLOG("user unknown on database, ignoring this module");
      }
      return PAM_IGNORE;

   }

   if( flags & PAM_PRELIM_CHECK ) {

      /* Se usuario nao eh root, deve digitar senha atual novamente */
      if(getuid() != 0) {

         conn = init_db(modopt);
         if(conn == NULL) {

            if(modopt->debug >= 2) {
               SYSLOG("connection string is null, ignoring this module");
            }
            return PAM_IGNORE;

         }

         get_passwd(pamh, &passwd, PAM_OLDAUTHTOK, PASSWD_PROMPT_ACTUAL);
         const char *sha1 = encrypt_password(PW_SHA1, passwd, NULL);
         verifypass = verify_password(conn, user, sha1);

         free_db(conn);

         if(verifypass != 1) {
            return PAM_AUTH_ERR;
         }

      } else {

         if(modopt->debug >= 1) {
            SYSLOG("success password change for '%s'", user);
         }
         return PAM_SUCCESS;

      }

   } else if( flags & PAM_UPDATE_AUTHTOK ) {

      retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &oldauth);
      passwd = (char *) oldauth;

       if(getuid() != 0) {

          conn = init_db(modopt);

          get_passwd(pamh, &passwd, PAM_OLDAUTHTOK, PASSWD_PROMPT_ACTUAL);
          const char *sha1 = encrypt_password(PW_SHA1, passwd, NULL);
          verifypass = verify_password(conn, user, sha1);

          free_db(conn);

          if(verifypass != 1) {
             return PAM_AUTH_ERR;
          }

       }

       get_passwd(pamh, &newpasswd, PAM_AUTHTOK, PASSWD_PROMPT_NEW);
       if(strlen(newpasswd) < 8) {
          printf(PASSWD_SMALL);
          exit(0);
       }

       pam_set_item(pamh, PAM_AUTHTOK, (void *) NULL);
       get_passwd(pamh, &confirmpasswd, PAM_AUTHTOK, PASSWD_PROMPT_CONFIRM);

       if(strcmp(newpasswd, confirmpasswd)==0) {

          conn = init_db(modopt);

          const char *sha1 = encrypt_password(PW_SHA1, newpasswd, NULL);
          modify_password(conn, user, sha1);

          free_db(conn);

          return PAM_SUCCESS;

       } else {

          printf(PASSWD_DONT_MATCH);
          exit(0);

       }

   }

   return PAM_SUCCESS;

}

PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
                                     int argc, const char **argv) {

   return (PAM_SUCCESS);

}

PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
                                     int argc, const char **argv) {

   return (PAM_SUCCESS);

}

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
                              int argc, const char *argv[]) {

   return (PAM_SUCCESS);

}

/* ****************************************
 * private: get_passwd
 * Ask for password
 * **************************************** */
int get_passwd(pam_handle_t *pamh, char **pass, int flag, char *msg_get) {

   struct pam_message message;
   struct pam_response *resp;
   const struct pam_message *messages[1];
   const struct pam_conv *pconv;
   const void *item=NULL, *conv=NULL;
   int retval;

   pam_get_item(pamh, flag, &item);

   if(item == NULL) {

      retval = pam_get_item(pamh, PAM_CONV, (const void**) &conv );
         if(retval != PAM_SUCCESS)
            return PAM_CONV_ERR;
   
      message.msg_style = PAM_PROMPT_ECHO_OFF;
      message.msg = msg_get;
      messages[0] = &message;
   
      pconv = (const struct pam_conv *) conv;

      pconv->conv(1, messages, &resp, pconv->appdata_ptr);
      if ((retval = pam_set_item(pamh, flag, resp[0].resp)) != PAM_SUCCESS)
         return retval;

      *pass = (char *) malloc(strlen(resp[0].resp));
      strcpy(*pass, resp[0].resp);

      free(resp);

   }

   return PAM_SUCCESS;

}

#ifdef PAM_STATIC
/* static module data */
struct pam_module _pam_mysql_modstruct = {
    PAM_MODULE_NAME,
    pam_sm_authenticate,
    pam_sm_setcred,
    pam_sm_acct_mgmt,
    pam_sm_open_session,
    pam_sm_close_session,
    pam_sm_chauthtok
};

#endif
